/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */

import { strict as assert } from "node:assert";

import {
	JsonAsTree,
	SchemaFactory,
	SchemaFactoryAlpha,
	TableSchema,
	TreeViewConfiguration,
	// eslint-disable-next-line import-x/no-internal-modules
} from "@fluidframework/tree/internal";
import type {
	areSafelyAssignable,
	requireTrue,
	requireAssignableTo,
	// eslint-disable-next-line import-x/no-internal-modules
} from "@fluidframework/tree/internal";

import { Empty001, Empty020, largeUnion } from "../largeExport.js";
import { BadArraySelf, GoodArraySelf, RecursiveMap, RecursiveRecord } from "../testExports.js";

describe("import tests", () => {
	it("recursive record", () => {
		const r = new RecursiveRecord({
			x: new RecursiveRecord({}),
			y: 42,
		});
		assert.deepEqual(Object.keys(r), ["x", "y"]);
	});

	it("recursive map", () => {
		const r = new RecursiveMap([["", new RecursiveMap([])]]);
		assert.equal(r.size, 1);
	});

	it("JsonArray", () => {
		const r = new JsonAsTree.Array([1]);
		assert.equal(r[0], 1);
	});

	it("JsonObject", () => {
		const r = new JsonAsTree.JsonObject({ x: 1 });
		assert.equal(r.x, 1);

		const f = new SchemaFactory("com.example");
		class ObjectHolder extends f.object("use", { x: JsonAsTree.JsonObject }) {}
	});

	// The TableSchema code is complex and previously had issues where invalid `.d.ts` files were generated by TypeScript.
	// These tests exist to ensure we don't inadvertently check in changes that regress users' ability to consume that code.
	it("TableSchema", () => {
		const schemaFactory = new SchemaFactoryAlpha("com.example");

		const Cell = schemaFactory.string;

		class ColumnProps extends schemaFactory.object("table-column-props", {
			label: schemaFactory.optional(schemaFactory.string),
		}) {}
		class Column extends TableSchema.column({
			schemaFactory,
			cell: Cell,
			props: schemaFactory.optional(ColumnProps),
		}) {}

		class RowProps extends schemaFactory.object("table-row-props", {
			label: schemaFactory.optional(schemaFactory.string),
		}) {}
		class Row extends TableSchema.row({
			schemaFactory,
			cell: Cell,
			props: RowProps,
		}) {}

		class Table extends TableSchema.table({
			schemaFactory,
			cell: Cell,
			column: Column,
			row: Row,
		}) {}

		const _table = Table.create({
			columns: [
				new Column({
					props: {
						label: "Column 0",
					},
				}),
				new Column({
					props: {
						label: "Column 1",
					},
				}),
			],
			rows: [
				new Row({
					cells: {},
					props: {
						label: "Row 0",
					},
				}),
				new Row({
					cells: {},
					props: {
						label: "Row 1",
					},
				}),
			],
		});
	});

	// See also the unit tests for JsonAsTree in tree's jsonDomainSchema.spec.ts
	it("Iterator types", () => {
		type ImportedArrayNodeIterator = ReturnType<JsonAsTree.Array[typeof Symbol.iterator]>;
		type ImportedObjectNodeIterator = ReturnType<
			JsonAsTree.JsonObject[typeof Symbol.iterator]
		>;

		type ArrayIterator = ReturnType<
			(readonly (
				| string
				| number
				| boolean
				| JsonAsTree.JsonObject
				| JsonAsTree.Array
				| null
			)[])[typeof Symbol.iterator]
		>;

		type ObjectIterator = IterableIterator<
			[string, string | number | boolean | JsonAsTree.JsonObject | JsonAsTree.Array | null]
		>;

		type _checkArray = requireTrue<
			areSafelyAssignable<ImportedArrayNodeIterator, ArrayIterator>
		>;

		type _checkObject = requireTrue<
			areSafelyAssignable<ImportedObjectNodeIterator, ObjectIterator>
		>;
	});

	it("BadArraySelf", () => {
		const b = new BadArraySelf([new BadArraySelf([new BadArraySelf([])])]);
		const inner = b[0] ?? assert.fail();
		const inner2 = inner[0];
		type B = typeof b;
		type Inner = typeof inner;
		type Inner2 = typeof inner2;
		type _check1 = requireAssignableTo<undefined, Inner2>;

		// This undesired assignment is permitted due to schema-aware types being mangled by `any` from d.ts file. See note on BadArraySelf.
		// Intellisense thinks this is an error because it's not using the d.ts files and instead using the actual source which has correct typing.
		// "build" tests (which compile these tests and the source in this package together) have the same behavior as intellisense, so this is disabled.
		// type _check2 = requireAssignableTo<number, Inner2>;
	});

	it("GoodArraySelf", () => {
		const b = new GoodArraySelf([new GoodArraySelf([new GoodArraySelf([])])]);
		const inner = b[0] ?? assert.fail();
		const inner2 = inner[0];
		type B = typeof b;
		type Inner = typeof inner;
		type Inner2 = typeof inner2;
		type _check1 = requireAssignableTo<undefined, Inner2>;
		// @ts-expect-error This fails, like it should, due to working schema aware types
		type _check2 = requireAssignableTo<number, Inner2>;
	});

	it("LargeImport", () => {
		const schema = new SchemaFactory("com.example");

		class LargeUnionObjectNode extends schema.object("ObjectNode", { x: largeUnion }) {}

		const config = new TreeViewConfiguration({
			schema: LargeUnionObjectNode,
			enableSchemaValidation: true,
		});

		const node = new LargeUnionObjectNode({
			// eslint-disable-next-line unicorn/no-null
			x: new Empty020({ x: new Empty001({ x: null }) }),
		});
	});
});
